package vben.init;

import cn.hutool.core.lang.ClassScanner;
import cn.hutool.core.util.StrUtil;
import org.springframework.web.bind.annotation.*;
import vben.core.common.mvc.dao.JdbcDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import vben.core.framework.security.authz.AuthzApiData;
import vben.core.framework.security.pojo.Yapi;
import vben.core.framework.security.pojo.Yurl;

import java.lang.reflect.Method;
import java.util.*;

/**
 * API接口初始化，每次修改代码重启后，都会从代码搜集比对需要认证的请求URL，生成唯一的权限码存入数据库
 * 并将最新的需要认证的Url根据Method分类放到公共内存，用于用户每次请求时获取权限码做权限认证
 */
@Component
public class SysApiInit {

    protected void initApi() {
        //扫描Controller中的所有Mapping方法，得到需要认证的URL集合
        List<Yurl> codeUrlList = getScanList();

        //从数据库获取所有已存的URL集合
        List<String> dbUrlList = jdbcDao.findStringList("select id from sys_api_main");

        //收集最大权限位及最大权限位的最大权限码
        String sql = "select max(u.pos) as pos,max(u.code) as code from sys_api_main u where u.pos = (select max(uu.pos) from sys_api_main uu)";
        Map<String, Object> maxMap = jdbcDao.findMap(sql);
        int pos = 0;
        long code = 0;
        if (maxMap != null && maxMap.get("pos") != null) {
            pos = Integer.parseInt(String.valueOf(maxMap.get("pos")));
            code = Long.parseLong(String.valueOf(maxMap.get("code")));
        }

        //比较两个集合，计算得到需要插入数据库的insertList与需要更新数据库的updateList
        List<Object[]> insertList = new ArrayList<Object[]>();
        List<Object[]> updateList = new ArrayList<Object[]>();
        for (Yurl yurl : codeUrlList) {
            boolean flag = false;
            for (String dbUrlstr : dbUrlList) {
                if (yurl.getId().equals(dbUrlstr)) {
                    flag = true;
                    break;
                }
            }
            if (!flag) {//插入
                if (yurl.getType() == null) {
                    Object[] objects = new Object[7];
                    objects[0] = yurl.getId();
                    objects[1] = yurl.getPid();
                    objects[2] = 0;
                    objects[3] = 0;
                    objects[4] = yurl.getUrl();
                    objects[5] = yurl.getType();
                    objects[6] = new Date();
                    insertList.add(objects);
                } else {
                    if (code >= (1L << 62)) {
                        pos = pos + 1;
                        code = 1;
                    } else {
                        if (code == 0) {
                            code = 1;
                        } else {
                            code = code << 1;
                        }
                    }
                    Object[] objects = new Object[7];
                    objects[0] = yurl.getId();
                    objects[1] = yurl.getPid();
                    objects[2] = pos;
                    objects[3] = code;
                    objects[4] = yurl.getUrl();
                    objects[5] = yurl.getType();
                    objects[6] = new Date();
                    insertList.add(objects);
                }
            } else {//更新
                Object[] objects = new Object[3];
                objects[0] = yurl.getPid();
                objects[1] = yurl.getType();
                objects[2] = yurl.getId();
                updateList.add(objects);
            }
        }
        //设置是否为公共资源
//        for (Object[] objects : insertList) {
//            if (objects[0].toString().contains("_c")) {
//                objects[5] = 1;
//            }
//        }

        //批量插入与更新数据库
        String initSql = "UPDATE sys_api_main SET avtag=0";
        String updateSql = "UPDATE sys_api_main SET pid=?,type=?,avtag=1 WHERE id=?";
        String insertSql = "INSERT INTO sys_api_main(id,pid,pos,code,url,type,crtim,avtag) VALUES(?,?,?,?,?,?,?,1)";
        jdbcDao.update(initSql);
        jdbcDao.batch(updateSql, updateList);
        jdbcDao.batch(insertSql, insertList);
        AuthzApiData.AUTHPOS = pos;

        //初始化所有用户权限
        String updateUserSql = "update sys_org_user set catag=0";
        jdbcDao.update(updateUserSql);

        String updateCoopUserSql = "update sys_coop_user set catag=0";
        jdbcDao.update(updateCoopUserSql);

        //将所有要求认证的Url分Method放入内存，用于每次请求时的权限校验
        String g1ApiSql = "SELECT url,pos,code from sys_api_main where avtag=1 and type='get' and id not like '%}:get'";
        AuthzApiData.GET1_APIS = findDbApis(g1ApiSql);

        String g2ApiSql = "SELECT url,pos,code from sys_api_main where avtag=1 and type='get' and id like '%}:get'";
        AuthzApiData.GET2_APIS = findDbApis(g2ApiSql);

        String postApiSql = "SELECT url,pos,code from sys_api_main where avtag=1 and type='post'";
        AuthzApiData.POST_APIS = findDbApis(postApiSql);

        String putApiSql = "SELECT url,pos,code from sys_api_main where avtag=1 and type='put'";
        AuthzApiData.PUT_APIS = findDbApis(putApiSql);

        String deleteApiSql = "SELECT url,pos,code from sys_api_main where avtag= 1 and type='delete'";
        AuthzApiData.DELETE_APIS = findDbApis(deleteApiSql);

        String patchApiSql = "SELECT url,pos,code from sys_api_main where avtag=1 and type='patch'";
        AuthzApiData.PATCH_APIS = findDbApis(patchApiSql);

    }

    //扫描Controller中的所有Mapping方法，得到需要认证的URL集合
    private List<Yurl> getScanList() {
        List<Yurl> list = new ArrayList<>();
        Set<Class<?>> classes = ClassScanner.scanPackage("vben");
        for (Class<?> clazz : classes) {
            RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
            if (requestMapping != null) {
                String classUrl = requestMapping.value()[0];
                //---> /authc 开头的不需要登录， /gen 只要登录就有权限  /sys 需要管理员才有权限  其余通过rbac权限机制判断
//                if (!url.startsWith("authc")&&!url.startsWith("gen")&&!url.startsWith("sys")) {
                if (!classUrl.startsWith("authc") && !classUrl.startsWith("gen")) {
                    Yurl topUrl = new Yurl();
                    topUrl.setId(classUrl.replaceAll("/", "-"));
                    if (!classUrl.startsWith("/")) {
                        classUrl = "/" + classUrl;
                    }
                    if (topUrl.getId().contains("-")) {
                        topUrl.setPid(topUrl.getId().split("-")[0]);
                    }
                    topUrl.setUrl(classUrl);
                    if(StrUtil.isNotBlank(topUrl.getId())){
                        list.add(topUrl);
                    }
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        GetMapping getMapping = method.getAnnotation(GetMapping.class);
                        if (getMapping != null) {
                            Yurl yurl = new Yurl();
                            String methodUrl = "";
                            if (getMapping.value().length > 0) {
                                methodUrl = getMapping.value()[0];
                                yurl.setUrl(topUrl.getUrl() + "/" + methodUrl);
                                methodUrl = methodUrl.replaceAll("/", "-");
                                yurl.setId(topUrl.getId() + "-" + methodUrl + ":get");
                            } else {
                                yurl.setId(topUrl.getId() + ":get");
                                yurl.setUrl(topUrl.getUrl());
                            }
                            yurl.setPid(topUrl.getId());
                            yurl.setType("get");
                            list.add(yurl);
                            continue;
                        }

                        PostMapping postMapping = method.getAnnotation(PostMapping.class);
                        if (postMapping != null) {
                            Yurl yurl = new Yurl();
                            String methodUrl = "";
                            if (postMapping.value().length > 0) {
                                methodUrl = postMapping.value()[0];
                                yurl.setUrl(topUrl.getUrl() + "/" + methodUrl);
                                methodUrl = methodUrl.replaceAll("/", "-");
                                yurl.setId(topUrl.getId() + "-" + methodUrl + ":post");
                            } else {
                                yurl.setId(topUrl.getId() + ":post");
                                yurl.setUrl(topUrl.getUrl());
                            }
                            yurl.setPid(topUrl.getId());
                            yurl.setType("post");
                            list.add(yurl);
                            continue;
                        }

                        PutMapping putMapping = method.getAnnotation(PutMapping.class);
                        if (putMapping != null) {
                            Yurl yurl = new Yurl();
                            String methodUrl = "";
                            if (putMapping.value().length > 0) {
                                methodUrl = putMapping.value()[0];
                                yurl.setUrl(topUrl.getUrl() + "/" + methodUrl);
                                methodUrl = methodUrl.replaceAll("/", "-");
                                yurl.setId(topUrl.getId() + "-" + methodUrl + ":put");
                            } else {
                                yurl.setId(topUrl.getId() + ":put");
                                yurl.setUrl(topUrl.getUrl());
                            }
                            yurl.setPid(topUrl.getId());
                            yurl.setType("put");
                            list.add(yurl);
                            continue;
                        }

                        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
                        if (deleteMapping != null) {
                            Yurl yurl = new Yurl();
                            String methodUrl = "";
                            if (deleteMapping.value().length > 0) {
                                methodUrl = deleteMapping.value()[0];
                                yurl.setUrl(topUrl.getUrl() + "/" + methodUrl);
                                methodUrl = methodUrl.replaceAll("/", "-");
                                yurl.setId(topUrl.getId() + "-" + methodUrl + ":delete");
                            } else {
                                yurl.setId(topUrl.getId() + ":delete");
                                yurl.setUrl(topUrl.getUrl());
                            }

                            yurl.setPid(topUrl.getId());
                            yurl.setType("delete");
                            list.add(yurl);
                            continue;
                        }

                        PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
                        if (patchMapping != null) {
                            Yurl yurl = new Yurl();
                            String methodUrl = "";
                            if (patchMapping.value().length > 0) {
                                methodUrl = patchMapping.value()[0];
                                yurl.setUrl(topUrl.getUrl() + "/" + methodUrl);
                                methodUrl = methodUrl.replaceAll("/", "-");
                                yurl.setId(topUrl.getId() + "-" + methodUrl + ":patch");
                            } else {
                                yurl.setId(topUrl.getId() + ":patch");
                                yurl.setUrl(topUrl.getUrl());
                            }
                            yurl.setPid(topUrl.getId());
                            yurl.setType("patch");
                            list.add(yurl);
                            continue;
                        }

                    }
                }
            }
        }

        //设置请求URL的root根目录，方便后面API权限分配
        Set<String> rootSet = new HashSet<>();
        for (Yurl yurl : list) {
            if (yurl.getType() == null && yurl.getPid() != null) {
                rootSet.add(yurl.getPid());
            }
        }
        for (String str : rootSet) {
            Yurl yurl = new Yurl();
            yurl.setId(str);
            if(StrUtil.isNotBlank(yurl.getId())){
                list.add(yurl);
            }
        }
        return list;
    }

    //获取数据库请求的通用方法
    private List<Yapi> findDbApis(String sql) {
        return jdbcDao.getTp().query(sql, (rs, rowNum) -> {
            Yapi yperm = new Yapi();
            String url = rs.getString("url");
            if (url.endsWith("}")) {
                yperm.setUrl(url.substring(0, url.lastIndexOf("/")));
            } else {
                yperm.setUrl(url);
            }
            yperm.setPos(rs.getInt("pos"));
            yperm.setCode(rs.getLong("code"));
            return yperm;
        });
    }

    @Autowired
    private JdbcDao jdbcDao;


}